package cluster

import (
	"strings"
	"sync"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

type Status = byte

const (
	SNone Status = iota
	SLooking
	SLeader
	SFollower
	SSingle
)

type election struct {
	running bool

	room   *asteroid.Room
	logger def.Logger
	leader string
	status Status
	//mutex       sync.RWMutex
	mutualMutex sync.Mutex

	ticker  *time.Ticker
	timeout *time.Timer

	stop      chan struct{}
	stoped    chan struct{}
	vote      chan string
	notice    chan string
	success   chan string
	selection chan string
	accept    chan *voteMessage

	result    map[string]string
	count     map[string]int
	out       sync.Map
	logicTime int

	finish chan struct{}
	wait   bool
}

type voteMessage struct {
	status    byte
	node      string
	leader    string
	logicTime int
}

func newElection(room *asteroid.Room, logger def.Logger) *election {
	e := &election{
		running: false,
		room:    room,
		logger:  logger.LoggerWrap("Select", room.ID()),
		status:  SNone,
		//mutex:       sync.RWMutex{},
		mutualMutex: sync.Mutex{},

		stop:      make(chan struct{}),
		stoped:    make(chan struct{}),
		vote:      make(chan string),
		notice:    make(chan string),
		success:   make(chan string),
		selection: make(chan string),
		accept:    make(chan *voteMessage),

		out: sync.Map{},

		finish: make(chan struct{}),
		wait:   false,
	}
	room.SetTimeout(time.Second * 5)
	return e
}

func (e *election) Run() {
	e.mutualMutex.Lock()
	if e.running == true {
		e.mutualMutex.Unlock()
		return
	}
	e.running = true
	e.status = SLooking
	e.mutualMutex.Unlock()
	room := e.room
	room.On("vote", func(message asteroid.Message) {
		data := message.List()
		node := message.GetFrom()
		//fmt.Println("accept vote", node, data)
		leader := utils.ToString(data[0])
		logicTime := utils.ToInt(data[1])
		status := utils.ToByte(data[2])
		e.accept <- &voteMessage{
			status,
			node,
			leader,
			int(logicTime),
		}
	})
	room.On("hb", func(message asteroid.Message) {
		e.logger.Debugf("hb:%#v, %#v", room.CurrentNode(), e.status)
		if e.status == SLeader {
			message.Response(true)
		} else {
			message.Response(false)
		}
	})
	room.OnJoin(func(node string) {
		if len(room.OtherNodes()) > 2 {
			return
		}
		if e.ticker != nil {
			e.ticker.Stop()
		}
		e.logger.Debugf("join to reselection")
		e.reSelection()
	})
	room.OnLeave(func(target string) {
		if target != e.leader {
			return
		}
		if e.ticker != nil {
			e.ticker.Stop()
		}
		e.logger.Debugf("leave to reselection")
		e.reSelection()
	})
	room.On("loss", func(message asteroid.Message) {
		data := message.List()
		node := utils.ToString(data[0])
		e.logger.Debugf("loss:%#v", node)
		if e.status == SLooking || e.status == SNone {
			return
		}
		room.Direct(e.leader).CallbackTo(func(success bool, data interface{}) {
			if success && data.(bool) {
				return
			}
			if e.status == SLooking || e.status == SNone {
				return
			}
			e.status = SLooking
			e.out.Store(e.leader, true)
			e.leader = room.CurrentNode()
			logicTime := e.logicTime
			if e.timeout != nil {
				e.timeout.Stop()
			}
			e.timeout = time.AfterFunc(time.Second*1, func() {
				if e.status != SNone && logicTime == e.logicTime {
					e.selection <- room.CurrentNode()
				}
			})
		}).Send("hb")
		return
	})
	run := func() {
		for {
			select {
			case v := <-e.accept:
				e.logger.Debugf("accept: %s", v)
				if e.logicTime < v.logicTime {
					e.logicTime = v.logicTime
					e.initVote()
				} else if e.logicTime > v.logicTime {
					e.recover(v.node)
				}
				switch e.status {
				case SSingle, SLooking:
					if e.logicTime < v.logicTime {
						leader := e.updateLeader(v)
						go func() {
							if leader == "" {
								e.vote <- e.leader
							} else {
								e.vote <- leader
							}
						}()

					} else if e.logicTime == v.logicTime {
						leader := e.updateLeader(v)
						if leader != "" && leader != e.leader {
							go func() {
								e.vote <- leader
							}()
						}
					}
				case SFollower, SLeader:
					e.updateVote(v.node, v.leader)
					if v.status == SLooking {
						go func() {
							e.notice <- v.node
						}()
					}
				case SNone:
					e.updateVote(v.node, v.leader)
				}
			case leader := <-e.success:
				e.logger.Debugf("success: current %s, leader %s", room.CurrentNode(), leader)
				e.leader = leader
				if room.Count() == 1 {
					e.status = SSingle
				} else if e.leader == room.CurrentNode() {
					e.status = SLeader
				} else {
					e.status = SFollower
					e.startHB()
				}

				clusters := make([]string, len(e.result))
				i := 0
				for name, _ := range e.result {
					clusters[i] = name
					i++
				}

				if e.wait {
					e.finish <- struct{}{}
					e.wait = false
				}
			case leader := <-e.selection:
				e.leader = leader
				e.status = SLooking
				logicTime := nowTimestamp()
				e.logicTime = logicTime
				e.initVote()
				room.Other().List(e.leader, e.logicTime, e.status).Send("vote")
				if e.timeout != nil {
					e.timeout.Stop()
				}
				e.timeout = time.AfterFunc(time.Second*2, func() {
					if e.status != SNone && len(e.result) == 1 && logicTime == e.logicTime {
						//e.logger.Debugf("timeout: %s", e.result)
						e.success <- room.CurrentNode()
					}
				})
				e.logger.Debugf("start selection: current %s, leader %s", room.CurrentNode(), e.leader)
			case leader := <-e.vote:
				e.leader = leader
				e.result[room.CurrentNode()] = e.leader
				e.updateVote(room.CurrentNode(), e.leader)
				room.Other().List(e.leader, e.logicTime, e.status).Send("vote")
				e.logger.Debugf("vote: current %s, leader %s", room.CurrentNode(), e.leader)
			case node := <-e.notice:
				room.Direct(node).List(e.leader, e.logicTime, e.status).Send("vote")
				e.logger.Debugf("notice: current %s, leader %s", room.CurrentNode(), e.leader)
			case <-e.stop:
				e.status = SNone
				e.leader = ""

				e.stoped <- struct{}{}
				return
			}
		}
	}
	go run()
	time.AfterFunc(time.Second*3, func() {
		e.out = sync.Map{}
		if e.status == SNone {
			return
		}
		e.selection <- room.CurrentNode()
	})
}

func (e *election) judgeLeader(leader1 string, leader2 string) string {
	if strings.Compare(leader1, leader2) > 0 {
		return leader1
	} else {
		return leader2
	}
}

func (e *election) startHB() {
	if e.ticker != nil {
		e.ticker.Stop()
	}
	e.ticker = time.NewTicker(time.Minute * 1)
	go func() {
		for _ = range e.ticker.C {
			if e.status != SFollower || !e.running {
				break
			}
			e.room.Direct(e.leader).CallbackTo(func(success bool, data interface{}) {
				if !success || !data.(bool) {
					e.room.Other().List(e.leader).Send("loss")
					e.reSelection()
				}
			}).Send("hb")
		}
		e.ticker.Stop()
	}()
}

func (e *election) reSelection() {
	e.out.Store(e.leader, true)
	e.selection <- e.room.CurrentNode()
}

func (e *election) initVote() {
	e.result = map[string]string{e.room.CurrentNode(): e.leader}
	e.count = map[string]int{e.leader: 1}
}

func (e *election) recover(node string) {
	if oldLeader, ok := e.result[node]; ok {
		e.count[oldLeader]--
	}
}

func (e *election) updateVote(node string, leader string) {
	e.recover(node)
	e.result[node] = leader
	e.count[leader]++
}

func (e *election) updateLeader(v *voteMessage) string {
	if _, ok := e.out.Load(v.node); ok {
		e.out.Delete(v.node)
	}
	if _, ok := e.out.Load(v.leader); ok {
		e.room.Direct(v.node).List(v.leader).Send("loss")
	} else {
		e.updateVote(v.node, v.leader)
		if v.status == SLeader {
			return v.leader
		}
	}

	max := 0
	waiver := 0
	se := ""
	for l, t := range e.count {
		if l == "" {
			waiver = t
		} else if t > max {
			se = l
			max = t
		} else if t == max {
			se = e.judgeLeader(se, l)
		}
	}
	if e.leader != se {
		max++
	}
	if max > (e.room.Count()-waiver)/2 {
		if _, ok := e.out.Load(se); ok {
			e.room.Other().List(se).Send("loss")
			return e.leader
		}
		go func() {
			e.success <- se
		}()
		return se
	} else {
		return se
	}
}

func (e *election) Leader() string {
	e.mutualMutex.Lock()
	defer e.mutualMutex.Unlock()
	if e.status != SLooking {
		return e.leader
	} else {
		e.wait = true
		<-e.finish
	}
	return e.leader
}

func (e *election) AsyncLeader(c chan<- string) {
	go func() {
		leader := e.Leader()
		c <- leader
	}()
}

func (e *election) Close() {
	e.logger.Debugf("[ Election ] Closing")
	if !e.running {
		return
	}
	//fmt.Println("election stoping")
	e.stop <- struct{}{}
	<-e.stoped
	//fmt.Println("election stoped")
	e.running = false
}

func nowTimestamp() int {
	t := time.Now().UnixNano() / 1e9
	return int(t)
}
